namespace Bowlus.FrontEnd
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Threading;

    using Bowlus.Common;

    using Microsoft.WindowsAzure.ServiceRuntime;
    using Microsoft.Web.Administration;

    public class FrontEndManager
    {

        private IList<ArrFarm> currentFarms;
        
        private List<TenantDeployment> currentActiveTds;

        private void TenantProvisioned(TenantDeployment td)
        {
            td.Instance = td.Staging;
            td.InstanceId = td.StagingId;
            td.Status = TenantDeployment.S_ACTIVE;
            td.Staging = string.Empty;
            td.StagingId = string.Empty;
            td.SStatus = string.Empty;
            TentantDepTableContext.Instance.UpdateTD(td);
        }

        // sync can be safely called any times and at anytime.
        public void Sync()
        {
            try
            {
                var tds = TentantDepTableContext.Instance.TDQueryable.ToList();
                foreach (var td in tds)
                {
                    if (td.SStatus == TenantDeployment.S_PROVISIONED)
                        TenantProvisioned(td);
                }

                UpdateArrFarms();
                UpdateRoutingRules(tds);
            }
            catch (Exception ex)
            {
                Trace.TraceError(ex.Message + '\n' + ex.StackTrace);
                if (RoleEnvironment.IsEmulated)
                    throw;
            }
        }

        public void Initialize()
        {
            Trace.TraceInformation("Initialize FrontEndManager");

            // Config default FrontEnd site

            // try 3 times
            for (int i = 0; ; i++)
            {
                try
                {
                    // This site is defined in Azure .config file, and created by Azure deployer.
                    lock (BowlusHelper.ServerManagerLock)
                    {
                        var sm = BowlusHelper.ServerManager;
                        var siteName = RoleEnvironment.CurrentRoleInstance.Id + "_AppFrontend";
                        var site = sm.Sites[siteName];
                        var siteApplication = site.Applications.First();

                        var appPool = sm.ApplicationPools[siteApplication.ApplicationPoolName];
                        appPool.ManagedPipelineMode = ManagedPipelineMode.Classic;
                        appPool.ProcessModel.LoadUserProfile = false;

                        var localIpv4Address = BowlusHelper.GetHostIpAddress();

                        Enumerable.Select(
                            site.Bindings.Where(binding => binding.Protocol == "http"),
                            b => b["bindingInformation"] = string.Format("{0}:80:", localIpv4Address));
                    }
                    break;
                }
                catch (Exception ex)
                {
                    Trace.TraceError(ex.Message + '\n' + ex.StackTrace);
                    if (i == 3) throw;
                    Thread.Sleep(3000);
                }
            }

            // initiate FrontEnd server
            Sync();
        }

        // Update IIS URL Rewrite rules
        // Relevant: all 'active' tenants, all FrontEnd need to do this
        private void UpdateRoutingRules(List<TenantDeployment> tds)
        {
            //<rewrite>
            //    <rules>
            //        <clear />
            //        <rule name="test1.thecloudsamurai.com" stopProcessing="true">
            //            <match url=".*" />
            //            <conditions>
            //                <add input="{HTTP_HOST}" pattern="test1.thecloudsamurai.com" />
            //            </conditions>
            //            <action type="Rewrite" url="http://10.115.48.42/{R:0}" />
            //        </rule>
            //    </rules>
            //</rewrite>

            List<TenantDeployment> activeTds = new List<TenantDeployment>();
            foreach (var td in tds)
            {
                if (td.Status == TenantDeployment.S_ACTIVE)
                    activeTds.Add(td);
            }

            if (!hasActiveTDSChanged(activeTds)) return;

            Trace.TraceInformation("Update routing rule, count: " + activeTds.Count);

            // try 3 times
            for (int i = 0; ; i++)
            {
                try
                {
                    lock (BowlusHelper.ServerManagerLock)
                    {
                        var sm = BowlusHelper.ServerManager;
                        var config = sm.GetApplicationHostConfiguration();
                        var rules = config.GetSection("system.webServer/rewrite/rules");
                        rules.GetCollection().Clear();

                        foreach (var td in activeTds)
                        {
                            var rule = rules.GetCollection().CreateElement("rule");
                            rule.SetAttributeValue("name", td.HostName);
                            rule.SetAttributeValue("stopProcessing", true);
                            rules.GetCollection().Add(rule);
                            rule.GetChildElement("match").SetAttributeValue("url", ".*");
                            rule.GetChildElement("action").SetAttributeValue("type", "Rewrite");
                            rule.GetChildElement("action").SetAttributeValue("url", "http://" + td.Instance + "/{R:0}");

                            var conditions = rule.GetChildElement("conditions");

                            var condition = conditions.GetCollection().CreateElement("add");
                            condition.SetAttributeValue("input", "{HTTP_HOST}");
                            condition.SetAttributeValue("pattern", td.HostName);
                            conditions.GetCollection().Add(condition);

                            Trace.TraceInformation("Routing rule updated for: " + td.HostName);
                        }
                        sm.CommitChanges();
                    }
                    break;
                }
                catch (Exception ex)
                {
                    Trace.TraceError(ex.Message + '\n' + ex.StackTrace);
                    if (i == 3) throw;
                    Thread.Sleep(3000);
                }
            }

            currentActiveTds = activeTds;
        }

        // Removes all current farms and creates new farms, commit changes in IIS.
        private void UpdateArrFarms()
        {
            //<webFarms>
            //    <webFarm name="10.115.48.42" enabled="true">
            //        <server address="10.115.48.42" enabled="true">
            //            <applicationRequestRouting httpPort="8080" />
            //        </server>
            //        <applicationRequestRouting>
            //            <loadBalancing algorithm="WeightedRoundRobin" />
            //        </applicationRequestRouting>
            //    </webFarm>
            //    <applicationRequestRouting>
            //        <hostAffinityProviderList>
            //            <add name="Microsoft.Web.Arr.HostNameRoundRobin" />
            //            <add name="Microsoft.Web.Arr.HostNameMemory" />
            //        </hostAffinityProviderList>
            //    </applicationRequestRouting>
            //</webFarms>

            // check whether farms changes
            var arrFarms = CreateFarmsList();
            if (!hasFarmsChanged(arrFarms)) return;

            // try 3 times
            for (int i = 0; ; i++)
            {
                try
                {
                    lock (BowlusHelper.ServerManagerLock)
                    {
                        var sm = BowlusHelper.ServerManager;

                        // Remove all farms
                        {
                            var config = sm.GetApplicationHostConfiguration();
                            var farmsSection = config.GetSection("webFarms");
                            var farms = farmsSection.GetCollection();
                            farms.Clear();
                        }

                        // get the list of nodes in the Web Farm Role
                        // Dictionary<string, IPEndPoint> wwebFarmRoleNodes = new Dictionary<string, IPEndPoint>();
                        // foreach (RoleInstance instance in webfarmRole.Instances)
                        foreach (var arrFarm in arrFarms)
                        {
                            // foreach (var endpoint in instance.InstanceEndpoints.Where(e => e.Key == AppHostEndpointName))
                            // ?? creat a farm for each farm server?
                            foreach (var arrFarmServer in arrFarm.Servers)
                            {
                                var farm = CreateArrFarmConfigurationElement(sm, arrFarmServer.Endpoint.Host);
                                var farmServers = farm.GetCollection();
                                var server = farmServers.CreateElement("server");
                                server.SetAttributeValue("address", arrFarmServer.Endpoint.Host);
                                server.SetAttributeValue("enabled", true);
                                farmServers.Add(server);
                                server.GetChildElement("applicationRequestRouting").SetAttributeValue("httpPort", arrFarmServer.Endpoint.Port);

                                Trace.TraceInformation(string.Format("Adding farm server {0}:{1}", arrFarmServer.Endpoint.Host, arrFarmServer.Endpoint.Port));
                            }
                        }

                        sm.CommitChanges();
                    }
                    break;
                }
                catch (Exception ex)
                {
                    Trace.TraceError(ex.Message + '\n' + ex.StackTrace);
                    if (i == 3) throw;
                    Thread.Sleep(3000);
                }
            }

            currentFarms = arrFarms;
        }

        // calculate new deployment plan, write to TenantDeploymentTable
        public static void Rebalance()
        {
            var tds = TentantDepTableContext.Instance.TDQueryable.ToList();

            AppInstanceLocator.Rebalance(tds);

            TentantDepTableContext.Instance.UpdateTDs(tds);
        }

        private bool hasFarmsChanged(IList<ArrFarm> arrFarms)
        {
            if (currentFarms != null)
            {
                if (arrFarms.Count == currentFarms.Count)
                {
                    ArrFarm[] cfs = currentFarms.ToArray<ArrFarm>();
                    ArrFarm[] nfs = arrFarms.ToArray<ArrFarm>();
                    Array.Sort(cfs);
                    Array.Sort(nfs);
                    for (int i = 0; i < cfs.Length; i++)
                    {
                        if (cfs[i].CompareTo(nfs[i]) != 0)
                            return true;
                    }
                    return false;
                }
            }
            return true;
        }

        private bool hasActiveTDSChanged(List<TenantDeployment> activeTds)
        {
            if (currentActiveTds != null)
            {
                if (currentActiveTds.Count == activeTds.Count)
                {
                    TenantDeployment[] ctds = currentActiveTds.ToArray<TenantDeployment>();
                    TenantDeployment[] atds = activeTds.ToArray<TenantDeployment>();
                    Array.Sort(ctds);
                    Array.Sort(atds);
                    for (int i = 0; i < ctds.Length; i++)
                    {
                        if (ctds[i].CompareTo(atds[i]) != 0)
                            return true;
                    }
                    return false;
                }
            }
            return true;
        }


        // Use ServerManager to acturally create ArrFarm element.
        private static ConfigurationElement CreateArrFarmConfigurationElement(ServerManager sm, string farmName)
        {
            var config = sm.GetApplicationHostConfiguration();

            var farmsSection = config.GetSection("webFarms");
            var farms = farmsSection.GetCollection();

            var farm = farms.CreateElement("webFarm");
            farm.SetAttributeValue("name", farmName);
            farm.SetAttributeValue("enabled", "true");
            farms.Add(farm);

            // We are currently using a single server
            // farm.GetChildElement("applicationRequestRouting").GetChildElement("affinity").SetAttributeValue("useCookie", true);
            farm.GetChildElement("applicationRequestRouting").GetChildElement("loadBalancing").SetAttributeValue("algorithm", "WeightedRoundRobin");

            return farm;
        }

        // create farm list data. not acturally make change to IIS.
        // create a farm for each apphost instance to control the routing rules outside of the ARR routing rules.
        private static IList<ArrFarm> CreateFarmsList()
        {
            var appHostRole = RoleEnvironment.Roles[BowlusSettings.AppHostRoleName];

            var serviceManagementWrapper = BowlusSettings.GetManagementService();
            var deployment = serviceManagementWrapper.GetDeploymentBySlot(BowlusSettings.ServiceName, BowlusSettings.DeploymentSlot);

            var arrFarms = new List<ArrFarm>();

            foreach (var instance in appHostRole.Instances)
            {
                foreach (var endpoint in instance.InstanceEndpoints.Where(e => e.Key == BowlusSettings.AppHostEndpointName))
                {
                    var deploymentFarm = deployment.RoleInstanceList.FirstOrDefault(f => f.InstanceName == instance.Id & f.InstanceStatus == "Ready");
                    if (null != deploymentFarm)
                    {
                        var farm = new ArrFarm();
                        var farmServer = new ArrFarmServer()
                        {
                            Endpoint = new Uri("http://" + endpoint.Value.IPEndpoint),
                            InstanceId = instance.Id
                        };
                        // TODO
                        farm.Name = farmServer.Endpoint.Host;
                        farm.Servers.Add(farmServer);
                        arrFarms.Add(farm);
                    }
                }
            }

            return arrFarms;
        }

    }
    #region Sample XML Rewrite URL and ARR Farm/Server

    //<?xml version="1.0" encoding="utf-8"?>
    //<configuration>
    //    <system.webserver>
    //        <rewrite>
    //            <rules>
    //                <clear />
    //                <rule name="trent.bowlus.net" patternsyntax="ecmascript" stopprocessing="true">
    //                    <match url=".*" />
    //                    <conditions logicalgrouping="matchall" trackallcaptures="false">
    //                        <add input="{http_host}" pattern="trent.bowlus.net" />
    //                    </conditions>
    //                    <action type="rewrite" url="http://trenttest/{r:0}" />
    //                </rule>
    //                <rule name="webrolewebappfarm" stopprocessing="true">
    //                    <match url=".*" />
    //                    <conditions logicalgrouping="matchall" trackallcaptures="false" />
    //                    <action type="rewrite" url="http://fullscale180/{r:0}" />
    //                </rule>
    //            </rules>
    //        </rewrite>
    //    </system.webserver>
    //</configuration>

    //<webFarms>
    //    <webFarm name="WebRoleWebAppFarm" enabled="true">
    //        <server address="10.115.80.73" enabled="true">
    //            <applicationRequestRouting httpPort="8080" />
    //        </server>
    //        <server address="10.115.72.84" enabled="true">
    //            <applicationRequestRouting httpPort="8080" />
    //        </server>
    //        <server address="10.115.88.49" enabled="true">
    //            <applicationRequestRouting httpPort="8080" />
    //        </server>
    //        <applicationRequestRouting>
    //            <affinity useCookie="true" />
    //            <loadBalancing algorithm="WeightedRoundRobin" />
    //        </applicationRequestRouting>
    //    </webFarm>
    //    <webFarm name="TrentTest" enabled="true">
    //        <server address="10.115.88.49" enabled="true">
    //            <applicationRequestRouting httpPort="8080" />
    //        </server>
    //    </webFarm>
    //    <applicationRequestRouting>
    //        <hostAffinityProviderList>
    //            <add name="Microsoft.Web.Arr.HostNameRoundRobin" />
    //            <add name="Microsoft.Web.Arr.HostNameMemory" />
    //        </hostAffinityProviderList>
    //    </applicationRequestRouting>
    //</webFarms>
    #endregion
}